﻿
package extremefx.i18n {
	import extremefx.DateTime;
	import extremefx.i18n.Calendar;	

	/**
	 * @author Marcelo Volmaro
	 * <p>This is the Hebrew calendar.</p>
	 * <p>The Hebrew calendar supports only years between 5343 A.M. and 6000 A.M.</p>
	 */
	public class HebrewCalendar extends Calendar {
		private static const HEBREW_ERA:uint = 1;
		
		private static const MIN:DateTime = new DateTime (1583, 1, 1, 0, 0, 0);
		private static const MAX:DateTime = new DateTime (2239, 9, 29, 11, 59, 59);
		
		/**
		 * The DateTime ticks for first day of year 5343 A.M.
		 */
		private static const MIN_TICKS:Number = 499147488000000;
		
		/**
		 * The number of DateTime ticks for the last day of year 6000 A.M.
		 */
		private static const MAX_TICKS:Number = 706783967999999;
		
		/**
		 * The minimum year in the A.M. era supported.
		 */
		private static const MIN_YEAR:int = 5343;
		
		public function HebrewCalendar() {
			super();
			_abbrEraNames = ["A.M."];
			_eraNames = ["Anno Mundi"];
			if (_twoDigitYearMax == 99) _twoDigitYearMax = 5790;
		}
		
		/**
		 * The maximum year supported in the A.M. era.
		 */
		protected override function _getMaxYear():int {
			return 6000;
		}
		
		private function _checkDateTime(pTime:DateTime):void{
			if (pTime.ticks < MIN_TICKS || pTime.ticks > MAX_TICKS)
			throw new RangeError("pTime", "Only hebrew years between 5343 and 6000, inclusive, are supported.");
		}
		
		private function _checkEra(pEra:Array):void {
			var era:uint = pEra[0];
			if (era == -1) era = HEBREW_ERA;
			if (era != HEBREW_ERA) throw new ArgumentError("Era value was not valid.");
			pEra[0] = era;
		}
		
		/**
		 * The method maps a flash Hebrew month to a Calendrical Calculations Hebrew month.  
		 */
		private function _ccMonth(pMonth:int, pYear:int):int {
			if (pMonth <= 6) {
				return 6+pMonth;
			}
			else {
				var l:int = CCHebrew._lastMonthOfYear(pYear);
				if (l == 12) {
					return pMonth-6;
					
				} else {
					return pMonth <= 7 ? 6+pMonth : pMonth-7;  
				}
			}
		}
		/**
		 * The method maps a Calendrical Calculations Hebrew month to a flash Hebrew month. 
		 */
		private function _month(pMonth:int, pYear:int):int {
			if (pMonth >= 7) {
				return pMonth - 6;
			} else {
				var l:int = CCHebrew._lastMonthOfYear(pYear);
				return pMonth + (l == 12 ? 6 : 7);
			}
		}
		
		protected override function _checkYE(pYear:int, pEra:Array):void {
			_checkEra(pEra);
			if (pYear < MIN_YEAR || pYear > _getMaxYear()) throw new RangeError("pYear", "Only hebrew years between 5343 and 6000, inclusive, are supported.");
		}
		
		protected function _checkYME(pYear:int, pMonth:int, pEra:Array):void {
			_checkYE(pYear, pEra);
			var l:int = CCHebrew._lastMonthOfYear(pYear);
			if (pMonth < 1 || pMonth > l) {
				throw new RangeError("pMonth", XString.format("Month must be between 1 and {0}.", l));
			}
		}
		
		protected function _checkYMDE(pYear:int, pMonth:int, pDay:int, pEra:Array):void {
			_checkYME(pYear, pMonth, pEra);
			_argumentInRange("pDay", pDay, 1, getDaysInMonth(pYear, pMonth, pEra[0]));
		}
		
		public override function get eras():Array {
			return [HEBREW_ERA];  
		}
		
		public override function set twoDigitYearMax(pYear:int):void{ 
			_argumentInRange ("value", pYear, MIN_YEAR, _getMaxYear());
			_twoDigitYearMax = pYear;
		}
		
		public override function addMonths(pTime:DateTime, pMonths:int):DateTime {
			var y:int, m:int, d:int;
			var t:DateTime;

			if (pMonths == 0) {
				t = pTime;
				
			} else {
				var rd:int = CCFixed._fromDateTime(pTime);
				var a:Array = CCHebrew._dmyFromFixed(rd);
				y = int(a[0]);
				m = int(a[1]);
				d = int(a[2]);
				
				m = _month(m, y);
				
				if (pMonths < 0) {
					while (pMonths < 0) {
						if (m+pMonths > 0) {
							m += pMonths;
							pMonths = 0;
						} else {
							pMonths += m;
							y -= 1;
							m = getMonthsInYear(y);
						}
					}
				}
				else {
					while (pMonths > 0) {
						var my:int = getMonthsInYear(y);
						
						if (m+pMonths <= my) {
							m += pMonths;
							pMonths = 0;
							
						} else {
							pMonths -= my-m+1;
							m = 1;
							y += 1;
						}
					}
				}
				
				t = toDateTime(y, m, d, 0, 0, 0, 0);
				t.add(pTime.timeOfDay);
			}
			
			_checkDateTime(t);
			return t;
		}
		
		public override function addYears(pTime:DateTime, pYears:int):DateTime {
			var rd:int = CCFixed._fromDateTime(pTime);
			var y:int, m:int, d:int;
			var a:Array = CCHebrew._dmyFromFixed(rd);
			y = int(a[0]);
			m = int(a[1]);
			d = int(a[2]);
				
			y += pYears;
			rd = CCHebrew._fixedFromDMY(d, m, y);
			var t:DateTime = CCFixed._toDateTime(rd);
			t.add(pTime.timeOfDay);
			_checkDateTime(t);
			return t;
		}
		
		public override function getDayOfMonth(pTime:DateTime):int {
			_checkDateTime(pTime);
			var rd:int = CCFixed._fromDateTime(pTime);
			return CCHebrew._dayFromFixed(rd);
		}
		
		public override function getDayOfWeek(pTime:DateTime):int {
			_checkDateTime(pTime);
			var rd:int = CCFixed._fromDateTime(pTime);
			return CCFixed._dayOfWeek(rd);
		}
		
		public override function getDayOfYear(pTime:DateTime):int {
			_checkDateTime(pTime);
			var rd:int = CCFixed._fromDateTime(pTime);
			var year:int = CCHebrew._yearFromFixed(rd);
			var rd1_7:int = CCHebrew._fixedFromDMY(1, 7, year);
			return rd - rd1_7 + 1;
		}
		
		public override function getDaysInMonth(pYear:int, pMonth:int, pEra:int = -1):int{
			_checkYME(pYear, pMonth, [pEra]);
			var ccmonth:int = _ccMonth(pMonth, pYear); 
			return CCHebrew._lastDayOfMonth(ccmonth, pYear);
		}
		
		public override function getDaysInYear(pYear:int, pEra:int = -1):int{
			_checkYE(pYear, [pEra]);
			var rd1:int = CCHebrew._fixedFromDMY(1, 7, pYear);
			var rd2:int = CCHebrew._fixedFromDMY(1, 7, pYear+1);
			return rd2 - rd1;
		}
		
		public override function getEra(pTime:DateTime):int {
			_checkDateTime(pTime);
			return HEBREW_ERA;
		}
		
		public override function getLeapMonth(pYear:int, pEra:int = -1):int{
			return isLeapMonth (pYear, 7, pEra) ? 7 : 0;
		}
		
		public override function getMonth(pTime:DateTime):int {
			_checkDateTime(pTime);
			var rd:int = CCFixed._fromDateTime(pTime);
			var a:Array = CCHebrew._myFromFixed(rd);
			return _month(int(a[1]), int(a[0]));
		}
		
		public override function getMonthsInYear(pYear:int, pEra:int = -1):int{
			_checkYE(pYear, [pEra]);
			return CCHebrew._lastMonthOfYear(pYear);
		}
		
		public override function getYear(pTime:DateTime):int {
			_checkDateTime(pTime);
			var rd:int = CCFixed._fromDateTime(pTime);
			return CCHebrew._yearFromFixed(rd);
		}
		
		public override function isLeapDay(pYear:int, pMonth:int, pDay:int, pEra:int = -1):Boolean{
			_checkYMDE(pYear, pMonth, pDay, [pEra]);
			return isLeapYear(pYear) && (pMonth == 7 || (pMonth == 6 && pDay == 30)); 
		}
		
		public override function isLeapMonth(pYear:int, pMonth:int, pEra:int = -1):Boolean{
			_checkYME(pYear, pMonth, [pEra]);
			return isLeapYear(pYear) && pMonth == 7; 
		}
		
		public override function isLeapYear(pYear:int, pEra:int = -1):Boolean{
			_checkYE(pYear, [pEra]);
			return CCHebrew._isLeapYear(pYear);
		}
		
		public override function toDateTime(pYear:int, pMonth:int, pDay:int, pHour:int, pMinute:int, pSecond:int, pMSecond:int, pEra:int = -1):DateTime {
			_checkYMDE(pYear, pMonth, pDay, [pEra]);
			_checkHMSM(pHour, pMinute, pSecond, pMSecond);
			
			var ccm:int = _ccMonth(pMonth, pYear);
			var rd:int = CCHebrew._fixedFromDMY(pDay, ccm, pYear);
			return CCFixed._toDateTime2(rd, pHour, pMinute, pSecond, pMSecond);
		}
		
		public override function toFourDigitYear (pYear:int):int {
			_argumentInRange ("pYear", pYear, 0, _getMaxYear() - 1);
			
			var baseExtra:int = _twoDigitYearMax % 100;
			var baseCentury:int = _twoDigitYearMax - baseExtra;
			
			if (pYear >= 100) return pYear;
			
			if (pYear <= baseExtra){
				return baseCentury + pYear;
				
			} else{
				return baseCentury + pYear - 100;
			}
		}
		
		public override function get algorithmType():uint{
			return CalendarAlgorithmType.LUNISOLAR;
		}
		
		public override function get minSupportedDateTime():DateTime {
			return MIN;
		}
		
		public override function get maxSupportedDateTime():DateTime {
			return MAX;
		}
		
	}
}
